import os
import re
import shutil
import traceback

import coreapi
import coreschema
import psutil
import requests
from multiprocessing import cpu_count
from django.forms.models import model_to_dict
from django.http import JsonResponse
from rest_framework import views, mixins, viewsets
from rest_framework.response import Response
from rest_framework.schemas import ManualSchema
from rest_framework.views import APIView

import basic
from ROOT.settings import BASE_DIR
from basic.models import EmailModel, ProcessNumber, UsersKeyModel
from basic.serializer import EmailSerializer, UsersKeySerializer
from binance_dgs.settings import Parameter as dgs_parameter
from binance_era.settings import Parameter as era_parameter
from binance_dgs.process import DGSProcessAppStart
from binance_era.process import ERAProcessAppStart
from utils.binance import Binance
from utils.np import np_round


class MailReception(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
    """
    retrieve:
    获取邮箱收件地址

    update:
    更新邮箱收件地址
    """
    queryset = EmailModel.objects.get(id=1)
    serializer_class = EmailSerializer

    def retrieve(self, request, *args, **kwargs):
        instance = self.queryset
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.queryset
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)


class GetRobotStatus(views.APIView):
    """
        (多个搜索词以,分隔)获取指定交易所、指定策略的机器人进程与状态数据（用于研判是否运行）
    """

    # docs文档参数
    schema = ManualSchema(
        description='获取机器人状态数据',
        fields=[
            coreapi.Field(name="exchange_domain", required=True, location="form",
                          schema=coreschema.String(description="交易所名称")),
            coreapi.Field(name="strategy_name", required=True, location="form",
                          schema=coreschema.String(description="策略名称"))
        ]
    )

    @staticmethod
    def post(request):
        """
        获取机器人状态数据（存在直接返回否则创建后再返回）
        """
        res_data = request.data
        res_obj = ProcessNumber.objects.get_or_create(exchange=res_data['exchange_domain'],
                                                      strategy=res_data['strategy_name'],
                                                      defaults={
                                                          "exchange": res_data['exchange_domain'],
                                                          "strategy": res_data['strategy_name']
                                                      })
        return JsonResponse({'data': model_to_dict(res_obj[0]), 'code': 200, 'msg': 'ok.'})


class UpdateRobotApiKey(mixins.UpdateModelMixin, viewsets.GenericViewSet):
    """
    更新机器人APIKEY
    """

    queryset = UsersKeyModel.objects.all()
    serializer_class = UsersKeySerializer

    def update(self, request, *args, **kwargs):
        self.queryset.update_or_create(
            exchange=request.data['exchange'], strategy=request.data['strategy'], defaults=request.data
        )
        return Response()


class Operation(APIView):
    """
    操作页接口
    """

    # docs文档参数
    schema = ManualSchema(
        description='操作页接口',
        fields=[
            coreapi.Field(name="exchange_domain", required=True, location="form",
                          schema=coreschema.String(description="交易所名称")),
            coreapi.Field(name="strategy_name", required=True, location="form",
                          schema=coreschema.String(description="策略名称")),
            coreapi.Field(name="pan_opening", required=True, location="form",
                          schema=coreschema.String(description="0:实盘, 1:模拟盘")),
            coreapi.Field(name="status", required=True, location="form",
                          schema=coreschema.String(description="-1:立即停止, 1:运行中, 0:平仓停止")),
        ]
    )

    def post(self, request):
        """

        :param request: 接收的数据
        :return:
        """

        if request.data['exchange_domain'] == basic.Exchange.binance:
            return self.ex_binance(request.data)

        elif request.data['exchange_domain'] == basic.Exchange.okx:
            return JsonResponse({'code': 200, 'msg': 'ok.'})

        else:
            return JsonResponse({'code': 31400, 'msg': '操作请求体错误.'})

    @staticmethod
    def ex_binance(res_data):
        """
        币安交易所
        :param res_data:
        :return:
        """
        global parameter
        try:
            if res_data['strategy_name'] == basic.Strategy.dgs:
                parameter = dgs_parameter()
            elif res_data['strategy_name'] == basic.Strategy.era:
                parameter = era_parameter()

            binance = Binance(
                parameter=parameter,
                exchange=res_data['exchange_domain'],
                strategy=res_data['strategy_name'],
                pan_opening=res_data['pan_opening']
            )
        except:
            return JsonResponse({'code': 31400, 'msg': '操作请求体错误.'})

        try:

            # binance.cache_clear()   # 清空

            if res_data['strategy_name'] == basic.Strategy.dgs:
                process = DGSProcessAppStart(binance=binance, request_data=res_data)

            elif res_data['strategy_name'] == basic.Strategy.era:
                process = ERAProcessAppStart(binance=binance, request_data=res_data)

            else:
                return JsonResponse({'code': 31400, 'msg': '操作请求体错误.'})

            # 启动
            if res_data['status'] == '1':
                return process.app_start()

            # 平仓停止
            elif res_data['status'] == '0':
                return process.position_stop()
            # 立即停止
            else:
                return process.now_stop()

        except:
            binance.logger_info(traceback.format_exc())
            return JsonResponse({'code': 31402, 'msg': '系统错误'})


class MonitorSystem(APIView):
    """
    监控页接口: 获取系统信息及日志文件名称
    """

    # docs文档参数
    schema = ManualSchema(
        description='监控页接口: 获取系统信息',
        fields=[
            coreapi.Field(name="exchange_domain", required=True, location="form",
                          schema=coreschema.String(description="交易所名称")),
            coreapi.Field(name="strategy_name", required=True, location="form",
                          schema=coreschema.String(description="策略名称")),
        ]
    )

    def post(self, request):

        global middleware, parameter

        res_data = request.data
        try:
            if res_data['strategy_name'] == basic.Strategy.dgs:
                parameter = dgs_parameter()
            elif res_data['strategy_name'] == basic.Strategy.era:
                parameter = era_parameter()

            if request.data['exchange_domain'] == basic.Exchange.binance:
                middleware = Binance(
                    parameter=parameter,
                    exchange=res_data['exchange_domain'],
                    strategy=res_data['strategy_name']
                )

            elif request.data['exchange_domain'] == basic.Exchange.okx:
                pass

            else:
                return JsonResponse({'code': 31400, 'msg': '操作请求体错误.'})
        except:
            return JsonResponse({'code': 31400, 'msg': '操作请求体错误.'})

        process_obj = ProcessNumber.objects.filter(exchange=res_data['exchange_domain'],
                                                   strategy=res_data['strategy_name']).first()

        try:
            data = {
                "system_info": {
                    "public_ip": self.get_public_ip(middleware),
                    "disk": self.get_disk_usage(),
                    "mem": self.get_memory_usage(),
                    "cpu": self.get_cpu_usage(),
                    "system": self.get_max_load(),
                    "cpu_number": f"({cpu_count()}核)",
                    "receive_network": self.receive_network(),
                    "send_network": self.send_network(),
                    "process": self.get_process_info(process_obj.process),
                },
                "logs_name": self.get_file_name()
            }
            return JsonResponse({'data': data, 'code': 200, 'msg': 'ok'})
        except:
            middleware.logger_info(traceback.format_exc())
            return JsonResponse({'code': 31504, 'msg': '系统信息获取错误'})

    @staticmethod
    def get_public_ip(midd, url="http://api.ipify.org/", cache_title="public_ip"):
        """ 获取公网IP地址 """
        public_ip = midd.cache_get(cache_title)
        if public_ip:
            return public_ip
        else:
            try:
                res = requests.get(url)
                midd.cache_set(cache_title, res.text)
                return res.text
            except:
                midd.logger_info(f"公网地址获取失败. {traceback.format_exc()}")
                return 'get error'

    @staticmethod
    def get_disk_usage():
        """ 磁盘使用率 """
        total, used, free = shutil.disk_usage('/')
        return f"{round(used / total * 100, 2)}%"

    @staticmethod
    def get_memory_usage():
        """ 内存使用率 """
        mem = psutil.virtual_memory()
        return f"{round(mem.used / mem.total * 100, 2)}%"

    @staticmethod
    def get_cpu_usage():
        """ CPU使用率 """
        return f"{psutil.cpu_percent()}%"

    @staticmethod
    def get_max_load():
        """ 最大系统负载率 """
        load = round(psutil.getloadavg()[2], 2)
        load_str = "高负载" if load > cpu_count() else "低负载"
        return f"{load} [{load_str}]"

    @staticmethod
    def receive_network():
        """ 接收流量 """
        net = psutil.net_io_counters()
        return '{0:.2f} Gb'.format(net.bytes_sent / 1024 / 1024 / 1024)

    @staticmethod
    def send_network():
        """ 发送流量 """
        net = psutil.net_io_counters()
        return '{0:.2f} Gb'.format(net.bytes_recv / 1024 / 1024 / 1024)

    @staticmethod
    def get_process_info(pids):
        """ 进程信息 """
        result = []
        try:
            if not pids:
                return []

            for pid in pids:
                process = psutil.Process(pid)

                info = {
                    'pid': pid,  # 进程PID
                    'status': process.status(),  # 进程状态 running
                }
                result.append(info)
            return result
        except:
            middleware.logger_warning(f"进程信息获取失败. {traceback.format_exc()}")
            return result

    @staticmethod
    def get_file_name(dirs=BASE_DIR / "logs"):
        """获取日志文件名称"""
        dirs = os.listdir(dirs)[1:]
        return [dirs[0]] + list(reversed(dirs[1:]))


class MonitorLogs(APIView):
    """
    监控页接口: 根据日志文件名称获取日志内容
    """

    dirs = BASE_DIR / "logs"  # 日志文件路径
    logs_max_number = 30  # 最大条数

    # docs文档参数
    schema = ManualSchema(
        description='监控页接口: 根据日志文件名称获取日志内容',
        fields=[
            coreapi.Field(name="exchange_domain", required=True, location="form",
                          schema=coreschema.String(description="交易所名称")),
            coreapi.Field(name="strategy_name", required=True, location="form",
                          schema=coreschema.String(description="策略名称")),
            coreapi.Field(name="logs_name", required=True, location="form",
                          schema=coreschema.String(description="日志文件名称")),
            coreapi.Field(name="logs_page", required=True, location="form",
                          schema=coreschema.Number(description="日志文件页码")),
        ]
    )

    def post(self, request):

        global middleware, parameter
        res_data = request.data

        try:
            if res_data['strategy_name'] == basic.Strategy.dgs:
                parameter = dgs_parameter()
            elif res_data['strategy_name'] == basic.Strategy.era:
                parameter = era_parameter()

            if request.data['exchange_domain'] == basic.Exchange.binance:
                middleware = Binance(
                    parameter=parameter,
                    exchange=res_data['exchange_domain'],
                    strategy=res_data['strategy_name']
                )

            elif request.data['exchange_domain'] == basic.Exchange.okx:
                pass

            else:
                return JsonResponse({'code': 31400, 'msg': '操作请求体错误.'})
        except:
            return JsonResponse({'code': 31400, 'msg': '操作请求体错误.'})

        try:
            data = middleware.cache_get(res_data["logs_name"])
            if not data:
                data = list(self.read_reverse_bigfile(middleware, self.dirs / res_data["logs_name"]))
                middleware.cache_set(res_data["logs_name"], data, 180)

            data = data[(res_data["logs_page"] - 1) * self.logs_max_number:res_data["logs_page"] * self.logs_max_number]
            return JsonResponse({'data': data, 'code': 200, 'msg': 'ok'})
        except:
            middleware.logger_info(traceback.format_exc())
            return JsonResponse({'code': 31505, 'msg': '日志内容获取错误'})

    @staticmethod
    def read_reverse_bigfile(binance, filepath, encoding='utf-8', separator=b'\n', single_size=1024 * 1024):
        """
        获取文件内容
        :param binance: 中间件接口
        :param filepath: 文件路径
        :param encoding: 字符编码，默认utf-8
        :param separator: 行尾分隔符，默认 '\n'
        :param single_size: 单次读取 字符量，默认 1024*1024
        :return: generator
        """
        with open(filepath, 'rb') as f:
            try:
                f.seek(0, 2)
                position = f.tell()
                if position > single_size:
                    f.seek(-single_size, 2)
                else:
                    f.seek(0, 0)
            except OSError:
                binance.logger_info(f"空白的文件.\n{traceback.format_exc()}")
                return '空白的文件'
            line = b''
            while 1:
                chunk = f.read(single_size)
                index_list = [match.end() for match in re.finditer(separator, chunk)]
                index = None
                while index_list:
                    target = index_list.pop()
                    if index is None:
                        line = chunk[target:] + line
                    else:
                        line = chunk[target:index] + line
                    if line:
                        yield line.decode(encoding=encoding)
                    line = b''
                    index = target
                else:
                    line = chunk[:index] + line
                position = f.tell()
                if position > 2 * single_size and single_size > 0:
                    f.seek(-2 * single_size, 1)
                else:
                    f.seek(0, 0)
                    single_size = position - single_size
                    if single_size <= 0:
                        yield line.decode(encoding=encoding)
                        return "结束"


class MineProfitLoss(APIView):
    """
    我的页面：7日盈亏比
    """

    # docs文档参数
    schema = ManualSchema(
        description='我的页面: 7日盈亏比',
        fields=[
            coreapi.Field(name="exchange_domain", required=True, location="form",
                          schema=coreschema.String(description="交易所名称")),
            coreapi.Field(name="strategy_name", required=True, location="form",
                          schema=coreschema.String(description="策略名称"))
        ]
    )

    def post(self, request):

        global middleware, parameter

        res_data = request.data
        try:
            if res_data['strategy_name'] == basic.Strategy.dgs:
                parameter = dgs_parameter()
            elif res_data['strategy_name'] == basic.Strategy.era:
                parameter = era_parameter()

            if request.data['exchange_domain'] == basic.Exchange.binance:
                middleware = Binance(
                    parameter=parameter,
                    exchange=res_data['exchange_domain'],
                    strategy=res_data['strategy_name']
                )
                if res_data['strategy_name'] == basic.Strategy.dgs:
                    return self.dgs_binance_history_trades(middleware)
                elif res_data['strategy_name'] == basic.Strategy.era:
                    return self.era_binance_history_trades(middleware)

            elif request.data['exchange_domain'] == basic.Exchange.okx:
                pass

        except:
            return JsonResponse({'code': 31400, 'msg': '操作请求体错误.'})

    @staticmethod
    def dgs_binance_history_trades(midd):

        def get_history_trades():
            """ 获取7日历史交易数据 """
            data_list = list()
            tradings = midd.cache_get(midd.parameter.db_coll_trading_symbol)
            if tradings:
                for trading in tradings:
                    trades = midd.rest("info", "get_account_trades", symbol=trading["symbol"],
                                       startTime=trading[midd.parameter.db_coll_balance_timeStamp])
                    if trades:
                        data_list.append(trades)
            return data_list

        data = list()
        trades_list = get_history_trades()
        for item in trades_list:
            symbol_data = {"symbol": item[0]["symbol"], "realizedPnl": 0}
            for t in item:
                symbol_data["realizedPnl"] += float(t["realizedPnl"])
            symbol_data["realizedPnl"] = np_round(symbol_data["realizedPnl"])
            data.append(symbol_data)

        return JsonResponse({'data': data, 'code': 200, 'msg': 'ok'})

    @staticmethod
    def era_binance_history_trades(midd):
        trades_list = midd.rest("info", "get_account_trades", symbol=midd.parameter.symbol)
        realized_pnl = 0
        for item in trades_list:
            realized_pnl += float(item["realizedPnl"])
        return JsonResponse({
            'data': [{"symbol": midd.parameter.symbol, "realizedPnl": np_round(realized_pnl)}],
            'code': 200,
            'msg': 'ok'
        })
